<?php
namespace App\Common\Lib\Cache;

use App\Common as com;
use App\Common\Lib\Cache\KeyDB as nameKeyDB;

/**
 * key数据库
 * @property nameKeyDB\DataStore $dataStore
 */
class KeyDB extends nameKeyDB\Base {

	use com\Cla\ClaBase\Traits\GoPool;

	/**
	 * 切换key
	 * @param string $keyPath
	 * @param array $param
	 * @return $this
	 */
	public function changeKey( string $keyPath, array $param = [] ):self {
		parent::__construct( $keyPath, $param );
		//
		return $this;
	}

	/**
	 * 获取缓存数据
	 * @param mixed $default 默认值。[false][默认]
	 * @return mixed 数据
	 * @throws
	 */
	public function get( $default = false ) {
		$cacheValue = $this->goRedis()->get();
		if( $cacheValue === false && $this->isPersistence() ){
			$cacheValue = $this->goPersistence()->get( $default );
			// 如果持久层不存在，则保存在缓存中60秒
			$this->newOfClone()->set( $cacheValue, $this->goPersistence()->ttlOfExpireOrDefault( 60 ) );
		}
		return $cacheValue !== false ? $this->unpack( $cacheValue ) : $default;
	}

	/**
	 * 获取缓存数据，如果不存在则写入
	 * @param mixed|\Closure $defaultOrFn 写入的数据，如果为闭包则把执行结果写入
	 * @param int|null $expire 有效时间，单位：秒。[null]永久 [int]必须大于0
	 * @return mixed
	 */
	public function getOrSet( $defaultOrFn, ?int $expire ) {
		if( ! $this->has() ){
			if( $defaultOrFn instanceof \Closure ){
				$value = $defaultOrFn();
			}else{
				$value = $defaultOrFn;
			}
			$this->set( $value, $expire );
			return $value;
		}
		//
		return $this->get();
	}

	/**
	 * 设置缓存数据
	 * @param mixed $value 存储数据
	 * @param int|null $expire 有效时间，单位：秒。[null]永久 [int]必须大于0
	 * @return bool [true][必返]
	 * @throws
	 */
	public function set( $value, ?int $expire ):bool {
		// ...
		if( $expire !== null && $expire <= 0 ){
			throw com\Lib\Err\Exception::makeOfNew( 'expire需大于0', com\Lib\Err\Code::$sysFunParamError );
		}
		$this->goRedis()->set( $value, $expire );
		//
		if( $this->isPersistence() ){
			$this->goPersistence()->set( $value, $expire );
		}
		//
		return true;
	}

	/**
	 * 读取
	 * @param string $keyDbPath 数组路径（支持格式：1.PHP数组访问的格式，如：['a'][0]['b']， 2."->"分割，如：a->b->c）。
	 * @param mixed $default 默认值。[false][默认]
	 * @return mixed 数据
	 */
	public function getOfPath( string $keyDbPath, $default = false ) {
		$keyDbData = $this->get();
		if( $keyDbData === false ){
			return $default;
		}
		//
		if( $keyDbPath !== null ){
			// 检查数据是否存在
			if( ! com\Lib\Fun\Array_::assignPathIsset( $keyDbData, $keyDbPath ) ){
				return $default;
			}
			//
			return com\Lib\Fun\Array_::assignPathRead( $keyDbData, $keyDbPath );
		}else{
			return $keyDbData;
		}
	}

	/**
	 * 读取（数据不存在则抛出异常）
	 * @param string $keyDbPath 数组路径（支持格式：1.PHP数组访问的格式，如：['a'][0]['b']， 2."->"分割，如：a->b->c）。
	 * @return mixed 数据
	 */
	public function getOfPathOrFail( string $keyDbPath ) {
		$value = $this->getOfPath( $keyDbPath );
		if( $value === false ){
			if( ! $this->has() ){
				throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataNotExist );
			}
		}
		//
		return $value;
	}

	/**
	 * 设置
	 * @param string $keyDbPath 数据路径（支持格式：1.PHP数组访问的格式，如：['a'][0]['b']， 2."->"分割，如：a->b->c）。
	 * @param mixed $value 数据
	 * @param int $expire 有效时间。[null][默认]跟随原数据剩余时间
	 * @return bool [true][必返]
	 */
	public function setOfPath( string $keyDbPath, $value, int $expire = null ):bool {
		$keyDbData = $this->get( [] );
		com\Lib\Fun\Array_::assignPathWrite( $keyDbData, $keyDbPath, $value );
		// 写入
		$this->set( $keyDbData, $expire ?? $this->ttlOrFalse() ?? null );
		//
		return true;
	}

	/**
	 * 设置
	 * @param array $keyDbPathArray 写入数据
	 * 如：[
	 * 		'a' => 1,
	 * 		'b' => [
	 * 			'c' => 2,
	 * 		],
	 * ]
	 * @param int $expire 有效时间。[null][默认]跟随原数据剩余时间
	 * @return bool [true][必返]
	 */
	public function setOfArray( array $keyDbPathArray, int $expire = null ):bool {
		$keyDbData = $this->get( [] );
		// 合并
		$keyDbData = com\Lib\Fun\Array_::arrayMerges( $keyDbData, $keyDbPathArray );
		// 写入
		$this->set( $keyDbData, $expire ?? $this->ttlOrFalse() ?? null );
		//
		return true;
	}

	/**
	 * 判断缓存是否存在
	 * @return boolean 是否存在 [true]存在 [false]不存在
	 */
	public function has(  ):bool {
		if( $this->goRedis()->has() ){
			return true;
		}
		//
		if( $this->isPersistence() && $this->goPersistence()->has() ){
			return true;
		}
		//
		return false;
	}

	/**
	 * 获取过期时间（根据ttl）
	 * @return int|false|null 剩余过期时间。[false]不存在 [null]永久。key 存在但没有设置剩余生存时间 [>0]剩余过期时间
	 * @throws
	 */
	public function ttlOrFalse() {
		$ttl = $this->ttl();
		if( $ttl > 0 ){
			return $ttl;
		}else if( -1 == $ttl ){
			return null;
		}else if( -2 == $ttl ){
			return false;
			// throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataNotExist );
		}else{
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataError );
		}
	}

	/**
	 * 获取过期时间（根据ttl）
	 * @return int|null 剩余过期时间。[null]永久。key 存在但没有设置剩余生存时间 [>0]剩余过期时间
	 * @throws
	 */
	public function ttlOrFail():?int {
		$ttl = $this->ttl();
		if( $ttl > 0 ){
			return $ttl;
		}else if( -1 == $ttl ){
			return null;
		}else if( -2 == $ttl ){
			throw com\Lib\Err\Exception::makeOfNew( '数据不存在，获取ttl失败', com\Lib\Err\Code::$sysDataNotExist );
		}else{
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataError );
		}
	}

	/**
	 * 获取过期时间戳
	 * @return int 剩余过期时间。[null]永久。key 存在但没有设置剩余生存时间 [>0]剩余过期时间
	 * @throws
	 */
	public function ttlToTimestamp() {
		$ttl = $this->ttl();
		if( $ttl > 0 ){
			return $ttl;
		}else if( -1 == $ttl ){
			return null;
		}else if( -2 == $ttl ){
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataNotExist );
		}else{
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataError );
		}
	}

	/**
	 * 设置ttl
	 * @param int|null $expire 有效时间。[null]永久
	 * @return void
	 */
	public function setTtl( ?int $expire ):void {
		if( $expire === null ){
			// 永久
			$cacheData = $this->get();
			if( false === $cacheData ) {
				throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataNotExist );
			}
			$this->set( $cacheData, $expire );
		}else{
			$this->goRedis()->goRedis()->expire( $this->getKeyName(), $expire );
		}
		//
		if( $this->isPersistence() ){
			$this->goPersistence()->setTtl( $expire );
		}
	}

	/**
	 * 命令以秒为单位返回 key 的剩余过期时间。
	 * @return int 剩余过期时间。[-2]key不存在时 [-1]key 存在但没有设置剩余生存时间 [>=0]剩余过期时间
	 * @throws
	 */
	public function ttl():int {
		$ttl = $this->goRedis()->ttl();
		if( $ttl === -2 && $this->isPersistence() ){
			return $this->goPersistence()->ttl();
		}
		return $ttl;
	}

	/**
	 * 自增缓存（针对数值缓存）
	 * @param int $step 步长
	 * @param int|null $expire 有效时间，单位：秒。[null]永久 [int]必须大于0
	 * @return int 修改后的值
	 */
	public function incX( int $step, ?int $expire ){
		$value = $this->goRedis()->incX( $step, $expire );
		if( $this->isPersistence() ){
			$this->goPersistence()->set( $value, $expire );
		}
		//
		return $value;
	}

	/**
	 * 自减缓存（针对数值缓存）
	 * @param int $step 步长
	 * @param int|null $expire 有效时间，单位：秒。[null]永久 [int]必须大于0
	 * @return int 修改后的值
	 */
	public function decX( $step, ?int $expire ){
		$value = $this->goRedis()->decX( $step, $expire );
		if( $this->isPersistence() ){
			$this->goPersistence()->set( $value, $expire );
		}
		//
		return $value;
	}

	/**
	 * 删除缓存
	 * @return bool [true][必返]
	 */
	public function del(  ):bool {
		$this->goRedis()->del();
		if( $this->isPersistence() ){
			$this->goPersistence()->del();
		}
		//
		return true;
	}

	/**
	 * 删除缓存(路径)
	 * @param string $keyDbPath 数据路径（支持格式：1.PHP数组访问的格式，如：['a'][0]['b']， 2."->"分割，如：a->b->c）
	 * @return void
	 */
	public function delOfPath( string $keyDbPath ) {
		$keyDbData = $this->get();
		com\Lib\Fun\Array_::assignPathUnset( $keyDbData, $keyDbPath );
		$ttl = $this->ttlOrFalse();
		if( $ttl !== false ){
			$this->set( $keyDbData, $ttl );
		}

	}

	/**
	 * 获取KeyName
	 * @return string KeyName
	 */
	public function getKeyName():string {
		$keyName = $this->keyPath;
		if( ! empty( $this->param ) ){
			$keyName .= '->' . implode( '.', $this->param );
		}
		return $keyName;
	}

	/**
	 * 获取缓存有效时间
	 * @param int $defaultExpire 默认值。[]
	 * @return int 缓存有效时间(秒)
	 */
	/*
	public function getExpire( $defaultExpire = 60 ) {
		return $defaultExpire;
	}
	*/

	/**
	 * 序列化
	 * @param $data
	 * @return string
	 */
	public function pack( $data ):string {
		if( is_array( $data ) ){
			return $this->_hyPackTag . serialize( $data );
		}else{
			return $data;
		}
	}

	/**
	 * 反序列化
	 * @param string $data
	 * @return mixed
	 */
	public function unpack( string $data ) {
		if( substr( $data, 0, $this->_hyPackTagLength ) === $this->_hyPackTag ){
			return unserialize( substr( $data, $this->_hyPackTagLength ) );
		}else{
			return $data;
		}
	}

	/**
	 * 是否使用持久层
	 * @return bool
	 */
	private function isPersistence(  ):bool {
		return $this->goPoolIsset( 'goPersistence' );
	}

	/**
	 * 持久层设置
	 * @param int|bool|null $expire 有效时间。[false]未设置 [null]永久 [>0]
	 * @param int|bool|null $delay 延迟。[false]未设置 [null]立刻写入 [0]立刻写入(异步，0秒后写入)
	 * @return $this
	 */
	public function persistence( $expire = false, $delay = false ):self {
		if( $expire !== false ){
			$this->goPersistence()->expire( $expire );
		}
		if( $delay !== false ){
			$this->goPersistence()->delay( $delay );
		}
		if( $expire === false && $delay === false ){
			$this->goPersistence();
		}
		//
		return $this;
	}

	/**
	 * 持久层延迟设置
	 * @param int|bool|null $delay 延迟。[false]未设置 [null]立刻写入 [0]立刻写入(异步，0秒后写入)
	 * @return $this
	 */
	public function persistenceDelay( $delay ):self {
		$this->goPersistence()->delay( $delay );
		//
		return $this;
	}

	/**
	 * 持久层
	 * @return KeyDB\_Persistence
	 */
	public function goPersistence():nameKeyDB\_Persistence {
		return $this->goPool( __FUNCTION__, function(){
		    return new nameKeyDB\_Persistence( $this->dataStore );
		} );
	}

	/**
	 * 缓存层->Redis
	 * @return KeyDB\_Cache
	 */
	public function goRedis():nameKeyDB\_Cache {
		return $this->goPool( __FUNCTION__, function(){
		    return new nameKeyDB\_Cache( $this->dataStore );
		} );
	}

}
